Markowitz Model¶

We will use the Markowitz Portfolio Optimization theory to select an optimal portfolio for us to invest in the tickers listed for Reto Actinver in order to asses the best strategy for winning the trading challenge.

The methodology was:

  • Download the stock prices with the yfinance library
  • Clean the data and calculate:
    • Mean Returns
    • Log Returns
    • Portfolio Risk
    • Portfolio Returns
    • Sharpe Ratio
  • Create a function that created random weights for selecting a 10 stock portfolio randomly
  • Create 100,000 random portfolios
  • Save the next best portfolio given the return

It is known that the efficient frontier can have multiple portfolios that could be called optimal given the risk-return tradeoff.

In [ ]:
 

Download the data from the YFinance library¶

[*********************100%***********************]  205 of 205 completed

53 Failed downloads:
- GICSA: No data found, symbol may be delisted
- VESTA: No data found, symbol may be delisted
- QVGMEX: No data found, symbol may be delisted
- HCITY: No data found, symbol may be delisted
- DLRTRAC: No data found, symbol may be delisted
- GMXT: No data found, symbol may be delisted
- ARA: No data found, symbol may be delisted
- FNOVA: No data found, symbol may be delisted
- VMEX: No data found, symbol may be delisted
- FUNO: No data found, symbol may be delisted
- EDUCA: No data found, symbol may be delisted
- CSPX: No data found, symbol may be delisted
- GPROFUT: No data found, symbol may be delisted
- SIMEC: No data found, symbol may be delisted
- GISSA: No data found, symbol may be delisted
- OXY1: No data found, symbol may be delisted
- VISTA: No data found, symbol may be delisted
- FMTY: No data found, symbol may be delisted
- NEMAK: No data found, symbol may be delisted
- BRKB: No data found, symbol may be delisted
- AUTLAN: No data found, symbol may be delisted
- MEDICA: No data found, symbol may be delisted
- AXTEL: No data found, symbol may be delisted
- FIHO: No data found, symbol may be delisted
- CETETRC: No data found, symbol may be delisted
- DANHOS: No data found, symbol may be delisted
- Q: No data found for this date range, symbol may be delisted
- KIMBER: No data found, symbol may be delisted
- ANGELD: No data found, symbol may be delisted
- SRET1: No data found, symbol may be delisted
- MFRISCO: No data found, symbol may be delisted
- TMM: No data found for this date range, symbol may be delisted
- FSHOP: No data found, symbol may be delisted
- PINFRA: No data found, symbol may be delisted
- TERRA: No data found, symbol may be delisted
- FPLUS: No data found, symbol may be delisted
- ICH: No data found for this date range, symbol may be delisted
- FIBRATC: No data found, symbol may be delisted
- DKNG1: No data found, symbol may be delisted
- VUAA: No data found, symbol may be delisted
- CULTIBA: No data found, symbol may be delisted
- IB1MXX: No data found, symbol may be delisted
- VINTE: No data found, symbol may be delisted
- GBM: No data found for this date range, symbol may be delisted
- HOTEL: No data found, symbol may be delisted
- SPORT: No data found, symbol may be delisted
- ALPEK: No data found, symbol may be delisted
- ORBIA: No data found, symbol may be delisted
- CORPTRC: No data found, symbol may be delisted
- ECAR: No data found, symbol may be delisted
- IB01: No data found, symbol may be delisted
- VALUEGF: No data found, symbol may be delisted
- DIABLOI: No data found, symbol may be delisted

Data handling and transformation¶

We will unstack the data two times in order to have a stock-date level table in order to have a cleaner data and calculate the returns

In [4]:
df=data.unstack()
df=df.unstack(level=1)
In [5]:
df=df.reset_index()
df=df.rename(columns={'level_0':'stock','level_1':'Date'})

Now let's apply a lambda function to have the log returns¶

In [6]:
df1=df[['stock','Date','Close']].fillna(0)
df1=df[['stock','Date','Close']].replace(np.nan,0)
df1=df1.loc[df1.Close!=0]
df1['pct_chg']=(df1.groupby("stock")['Close'].apply(pd.Series.pct_change))
df1["log_ret"]=(df1.groupby("stock")['Close'].apply(lambda x:np.log(1+x)))
df1=df1.replace(np.nan,0)
df1.head()
Out[6]:
stock Date Close pct_chg log_ret
0 AAL 2018-04-06 49.110222 0.000000 3.914225
1 AAL 2018-04-09 48.651245 -0.009346 3.905023
2 AAL 2018-04-10 46.346615 -0.047370 3.857495
3 AAL 2018-04-11 44.334938 -0.043405 3.814078
4 AAL 2018-04-12 45.477489 0.025771 3.838968
Out[7]:
stock Date Close pct_chg returns
0 AAL 2018-04-06 49.110222 0.000000 3.914225
1 AAL 2018-04-09 48.651245 -0.009346 3.905023
2 AAL 2018-04-10 46.346615 -0.047370 3.857495
3 AAL 2018-04-11 44.334938 -0.043405 3.814078
4 AAL 2018-04-12 45.477489 0.025771 3.838968

Pivoting the table to have columns of specific stocks¶

This will help for getting the Covariance Matrix

Out[8]:
stock AAL AAPL AAXJ ABNB ACWI AFRM ALFA AMC AMD AMZN ... X XLE XLF XLK XLV XOM XPEV YANG YINN ZM
Date
2018-04-09 -0.009346 0.009918 0.011941 NaN 0.005225 NaN 0.044879 0.000000 -0.008325 0.000605 ... 0.008698 0.004751 0.005521 0.007806 0.009890 0.000000 NaN -0.037163 0.034394 NaN
2018-04-10 -0.047370 0.018818 0.020191 NaN 0.015592 NaN 0.012314 0.021276 0.047219 0.021435 ... 0.031331 0.033102 0.015007 0.024632 0.016859 0.029384 NaN -0.093456 0.094158 NaN
2018-04-11 -0.043405 -0.004676 0.000129 NaN -0.004426 NaN -0.004943 -0.005952 -0.016032 -0.006385 ... -0.031772 0.010442 -0.011900 -0.005896 -0.008168 0.004671 NaN -0.003674 0.007384 NaN
2018-04-12 0.025771 0.009859 0.000257 NaN 0.005557 NaN 0.013155 -0.011976 0.026477 0.015031 ... 0.032815 -0.000142 0.017518 0.012015 0.006760 -0.002712 NaN -0.013232 0.008740 NaN
2018-04-13 -0.015031 0.003388 -0.009635 NaN -0.002211 NaN 0.000000 0.027273 -0.014881 -0.012227 ... -0.010312 0.011044 -0.015064 -0.003306 -0.000488 0.008029 NaN 0.036711 -0.038569 NaN

5 rows × 152 columns

In [9]:
df2=df2.fillna(0)
df2=df2.replace(np.nan,0)
data=df2.to_numpy()
cov_mat=df2.cov()*255
In [10]:
weights=np.random.rand(len(df1.stock.unique()),1)
weights=weights/np.sum(weights)
weights=np.array(weights).flatten()
weighRet=np.dot(weights,mean_returns)

What would our return be if we bought one stock off all the ones we queried?

Out[12]:
1.0300816026810875

And what about the risk?

0.9868668895858776

Now let's look at a Sharpe Ratio

In [17]:
shp_rt=port_ret/port_risk
print(shp_rt)
1.0437898094983653

This looks like a nice portfolio. However it is rather unreal to have 150 stocks in a portfolio for a trading challenge. We used 10 stock porfolio because one of the rules is to have traded at least in 5 symbols weekly in order to apply for winning the challenge.

stock
TQQQ    1.001910
LQDA    1.001444
LABU    1.000206
TZA     0.999146
UNG     0.999715
ACWI    1.000356
FINN    1.000573
XLF     1.000353
TGT     1.000988
JNJ     1.000393
dtype: float64
stock      TQQQ      LQDA      LABU       TZA       UNG      ACWI      FINN  \
stock                                                                         
TQQQ   0.600041  0.174267  0.587345 -0.489137  0.048426  0.143184  0.025644   
LQDA   0.174267  0.907587  0.289693 -0.199281  0.013764  0.045794  0.001139   
LABU   0.587345  0.289693  1.155326 -0.643373  0.025481  0.147156  0.024855   
TZA   -0.489137 -0.199281 -0.643373  0.637168 -0.049682 -0.145619 -0.030945   
UNG    0.048426  0.013764  0.025481 -0.049682  0.334412  0.014241  0.002079   
ACWI   0.143184  0.045794  0.147156 -0.145619  0.014241  0.041479  0.008057   
FINN   0.025644  0.001139  0.024855 -0.030945  0.002079  0.008057  0.045065   
XLF    0.140188  0.047766  0.144982 -0.182327  0.018367  0.047240  0.010534   
TGT    0.132335  0.043615  0.129791 -0.131337  0.009403  0.035440  0.004753   
JNJ    0.071629  0.013850  0.068219 -0.067494  0.008196  0.022469  0.004806   

stock       XLF       TGT       JNJ  
stock                                
TQQQ   0.140188  0.132335  0.071629  
LQDA   0.047766  0.043615  0.013850  
LABU   0.144982  0.129791  0.068219  
TZA   -0.182327 -0.131337 -0.067494  
UNG    0.018367  0.009403  0.008196  
ACWI   0.047240  0.035440  0.022469  
FINN   0.010534  0.004753  0.004806  
XLF    0.072986  0.039623  0.027305  
TGT    0.039623  0.120405  0.021609  
JNJ    0.027305  0.021609  0.042295  

All portflios look like this. A vector with mean returns and covariance Matrix

/var/folders/z0/5bwd40ds4xq0gh_lytnx56l80000gn/T/ipykernel_20789/2252656341.py:22: RuntimeWarning:

invalid value encountered in double_scalars

Iteration:  2
Iteration:  7
Iteration:  119
Iteration:  941
Iteration:  2497
Iteration:  2746
Iteration:  10298
Iteration:  28078
Return:  1274.0534644337909
***** Max Return Portfolio*************
Assets:  ['MEGA', 'BND', 'AMC', 'NVDA', 'MEGA', 'CIBR', 'T', 'SHV', 'NKLA']
Weights:  [0.20756599 0.00480686 0.13878128 0.02600988 0.30988935 0.0484763
 0.00499959 0.09458631 0.16488443]
Risk:  37822.1884441336
Return:  1274.0534644337909
Risk:  13997.072406278232
***** Min Variance Portfolio*************
Assets:  ['BKCH', 'MEGA', 'DRIP', 'IVV', 'EWZ', 'C', 'CIBR', 'WMT', 'SQ']
Weights:  [0.12347134 0.19151248 0.0820306  0.08815962 0.1333816  0.03620419
 0.07859314 0.06344978 0.20319725]
Risk:  13997.072406278232
Return:  534.5146058524512
***** Max Return Portfolio*************  0
Assets:  ['BABA', 'MEGA', 'BOTZ', 'GAP', 'MRO', 'SOFI', 'AMC', 'R', 'MRO']
Weights:  [0.0691337  0.17969215 0.15005989 0.1242482  0.173317   0.10182989
 0.07943755 0.0462223  0.07605933]
Risk:  131.342113159536
Return:  5.0773312631363074
***** Max Return Portfolio*************  1
Assets:  ['CIBR', 'LIT', 'ZM', 'SNAP', 'QLD', 'X', 'BYND', 'MEGA', 'PSQ']
Weights:  [0.16732598 0.05537036 0.03686758 0.06395675 0.10812245 0.06332081
 0.13644738 0.18243715 0.18615154]
Risk:  133.34704715025484
Return:  5.139409935116776
***** Max Return Portfolio*************  2
Assets:  ['COIN', 'PSQ', 'FAS', 'EWG', 'GCC', 'VRM', 'MEGA', 'C', 'AXP']
Weights:  [0.19560471 0.01940906 0.09416493 0.0790135  0.13549929 0.17976723
 0.20664032 0.02108621 0.06881476]
Risk:  151.03540381645817
Return:  5.687468463201288
***** Max Return Portfolio*************  3
Assets:  ['MEGA', 'SOXS', 'VEA', 'COIN', 'SPCE', 'INDA', 'WISH', 'AAL', 'AFRM']
Weights:  [0.27500109 0.01331601 0.16474675 0.12841989 0.02906515 0.13509613
 0.13369164 0.112317   0.00834634]
Risk:  200.99663285399717
Return:  7.238394606937323
***** Max Return Portfolio*************  4
Assets:  ['NKLA', 'INTC', 'ATER', 'GLD', 'TSLA', 'IVV', 'MEGA', 'AMC', 'INTC']
Weights:  [0.20588079 0.07508909 0.04579737 0.13574733 0.05838878 0.15767583
 0.28765449 0.01159391 0.0221724 ]
Risk:  210.25511249971532
Return:  7.526631815926474
***** Max Return Portfolio*************  5
Assets:  ['MEGA', 'LQDA', 'KOF', 'FAZ', 'MEGA', 'KOF', 'GLD', 'CSCO', 'MEGA']
Weights:  [0.12189519 0.05609805 0.09133075 0.11124932 0.15482056 0.13217375
 0.0959051  0.15459811 0.08192917]
Risk:  262.1205293645444
Return:  9.13707887412889
***** Max Return Portfolio*************  6
Assets:  ['SQQQ', 'YANG', 'VNQ', 'UPST', 'TAL', 'SQ', 'SOXL', 'MEGA', 'EWZ']
Weights:  [0.05804447 0.16363185 0.09813871 0.04018789 0.02687312 0.15004718
 0.02311439 0.39413944 0.04582295]
Risk:  288.06991324117706
Return:  9.942551162396056
***** Max Return Portfolio*************  7
Assets:  ['BBBY', 'SLV', 'WISH', 'WBD', 'MEGA', 'MEGA', 'AAXJ', 'IVV', 'JNUG']
Weights:  [0.11021666 0.02351872 0.06419566 0.12578174 0.22644056 0.21273307
 0.07775584 0.03249036 0.12686739]
Risk:  320.9902852874021
Return:  10.963765673278404